package newHtmlToWord;

import htmlToWord.CustomXWPFDocument;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.*;

/**
 * 描述:
 *
 * @author yanzhengwu
 * @create 2021-01-18 15:31
 */
public class HtmlToWord {

    public static void main(String[] args) throws IOException, IllegalArgumentException {

        Map<String, Object> data = new HashMap<>();
        data.put("CONTRACT_NAME", "测试这条数据能不能进去");

        HtmlToWord toWord = new HtmlToWord(data);


        //Document doc = Jsoup.parse(htmlStr);
        CustomXWPFDocument xwpfDocument = toWord.htmlToWord("");
        Long date = System.currentTimeMillis();
        String path = "F:/htmlToWord/" + date + "text.docx";
        OutputStream outputStream = new FileOutputStream(path);
        System.out.println("导出完成:" + path);
        xwpfDocument.write(outputStream);
        outputStream.close();

    }

    /**
     * 必须传入数据否则无法生成
     *
     * @param data
     */
    public HtmlToWord(Map<String, Object> data) throws IllegalArgumentException {
        if (null == data || data.size() == 0) {
            throw new IllegalArgumentException("传入数据不能为空！");
        }
        this.data = data;

    }

    /**
     * 需要填充的数据
     */
    private Map<String, Object> data;

    /**
     * 记录需要合并的区域
     */
    private List<CrossRangeCellMeta> crossRowEleMetaLs = new ArrayList<>();


    /**
     * TODO html字符串转word程序入口
     * @param htmlString
     * @return
     */
    public CustomXWPFDocument htmlToWord(String htmlString) throws IOException {
        String url = this.getClass().getResource("/评定表.html").getPath();
        String path = java.net.URLDecoder.decode(url, "utf-8");
        File input = new File(path);

        Document doc = Jsoup.parse(input, "UTF-8", "https://www.jb51.net/");
        //Document doc = Jsoup.parse(htmlStr);

        CustomXWPFDocument xWPFDocument = new CustomXWPFDocument();
        Elements elements = doc.select("body");
        if (null != elements && elements.size() > 0) {
            if (null != elements.get(0)) {
                replaceTagData(elements.get(0).children());
            }
        }

        wordDocFactory(xWPFDocument, elements);

        return xWPFDocument;
    }

    /**
     * <p>
     * createWordDocument
     * </p>
     *
     * @param xWPFDocument
     * @param elements
     * @author yanzhengwu
     */
    private void wordDocFactory(CustomXWPFDocument xWPFDocument, Elements elements) {
        Elements els = elements.select("body");
        if (null != els && els.size() > 0) {
            for (Element e : els.get(0).children()) {
                if ("p".equals(e.tagName())) {
                    XWPFParagraph xwpfParagraph = xWPFDocument.createParagraph();
                    readPTag(null, xwpfParagraph, e);
                }
                if ("table".equals(e.tagName())) {
                    readTableTag(xWPFDocument, elements);
                }

            }
            //合并单元格
            XWPFTable table = xWPFDocument.getTables().get(0);
            mergeCell(table);
        }

    }

    /**
     * 替换标签中的数据
     *
     * @param elements
     */
    private void replaceTagData(Elements elements) {
        for (int i = 0; i < elements.size(); i++) {
            Element element = elements.get(i);
            if (null != element) {
                readAllTag(element);
            }
        }
    }

    /**
     * 读取所有标签
     *
     * @param element
     */
    private void readAllTag(Element element) {
        if (!StringUtils.isEmpty(element.attr("data-source"))) {
            if (!StringUtils.isEmpty(element.attr("data-field"))) {
                String dataColumnKey = element.attr("data-field");
                if (data.containsKey(dataColumnKey)) {
                    Object obj = data.get(dataColumnKey);
                    if (!StringUtils.isEmpty(obj)) {
                        element.text(obj.toString());
                    }
                }
            }
        }

        //如果有子节点标签则继续读取
        if (null != element.children() && element.children().size() > 0) {
            for (Element el : element.children()) {
                readAllTag(el);
            }
        }
    }

    /**
     * 读取table标签中的值生成word
     *
     * @param xWPFDocument
     * @param elements
     */
    private void readTableTag(CustomXWPFDocument xWPFDocument, Elements elements) {
        List<Element> trLs = elements.select("tr");
        int rowSumCount = trLs.size();
        int columnCount = getSumColumn(trLs);
        //一次性生成完整表格，不支持畸形表格
        XWPFTable table = xWPFDocument.createTable(rowSumCount, columnCount);
        setCellWith(table);

        if (null != trLs && trLs.size() > 0) {
            for (int trRowIndex = 0; trRowIndex < trLs.size(); trRowIndex++) {
                Elements tdLs = trLs.get(trRowIndex).select("td");

                setDataCell(table, tdLs, trRowIndex);

            }
        }
    }

    /**
     * 获取td标签中所有的列数
     *
     * @param trLs
     * @return
     */
    private int getSumColumn(List<Element> trLs) {
        int columnCount = 0;
        Elements elements = trLs.get(0).select("td");
        for (int i = 0; i < elements.size(); i++) {
            Element tdEle = elements.get(i);
            columnCount += NumberUtils.toInt(tdEle.attr("colspan"), 1);
        }
        return columnCount;
    }


    /**
     * 设置表格每一个单元格的值
     *
     * @param table    当前表格
     * @param tdLs     html中的td标签
     * @param rowIndex 当前行下标
     */
    private void setDataCell(XWPFTable table, Elements tdLs, int rowIndex) {
        XWPFTableRow row = table.getRow(rowIndex);
        //记录插入数据列数
        int cellIndex = 0;
        for (int tdIndex = 0; tdIndex < tdLs.size(); tdIndex++) {
            Element tdEle = tdLs.get(tdIndex);
            int rowSpan = NumberUtils.toInt(tdEle.attr("rowspan"), 1);
            int colSpan = NumberUtils.toInt(tdEle.attr("colspan"), 1);
            readPTag(row.getCell(cellIndex), null, tdEle);
            if (colSpan > 1) { // 存在跨行或跨列
                CrossRangeCellMeta crossRangeCellMeta = new CrossRangeCellMeta(rowIndex, cellIndex, rowSpan, colSpan);
                String string1 = "开始行==" + crossRangeCellMeta.getFirstRow() + " ;开始列==" + crossRangeCellMeta.getFirstCol() + "  ;结束行==" + crossRangeCellMeta.getLastRow() + "  ;结束列==" + crossRangeCellMeta.getLastCol();
                crossRowEleMetaLs.add(crossRangeCellMeta);
            }

            cellIndex += colSpan;
        }
    }


    /**
     * 合并表格
     *
     * @param table
     */
    private void mergeCell(XWPFTable table) {
        for (CrossRangeCellMeta crcm : crossRowEleMetaLs) {
            mergeCellsHorizontal(table, crcm.getFirstRow(), crcm.getFirstCol(), crcm.getLastCol());
        }
    }


    /**
     * 只有第一次进入循环时开启 STMerge.RESTART
     *
     * @param table
     * @param startRow  开始行数
     * @param startCell 开始列数
     * @param endCell   结束列数
     */
    private void mergeCellsHorizontal(XWPFTable table, int startRow, int startCell, int endCell) {
        for (int cellIndex = startCell; cellIndex <= endCell; cellIndex++) {
            XWPFTableCell cell = table.getRow(startRow).getCell(cellIndex);
            if (cellIndex == startCell) {
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
            } else {
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
            }
            setCellWith(cell, 3000);
        }
    }

    /**
     * 跨行合并表格
     * 只有第一次进入循环时开启 STMerge.RESTART
     *
     * @param table
     * @param startCell 开始列数
     * @param startRow  开始行数
     * @param endRow    结束行数
     */
    private void mergeCellsVertically(XWPFTable table, int startCell, int startRow, int endRow) {
        for (int rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
            XWPFTableCell cell = table.getRow(rowIndex).getCell(startCell);
            if (rowIndex == startRow) {
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
            } else {
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * 读取P标签
     *
     * @param cell
     * @param xwpfParagraph
     * @param element
     */
    private void readPTag(XWPFTableCell cell, XWPFParagraph xwpfParagraph, Element element) {
        if (null == xwpfParagraph) {
            xwpfParagraph = cell.addParagraph();
        }
        String ownText = element.ownText();
        if (!StringUtils.isEmpty(ownText)) {
            XWPFRun run = xwpfParagraph.createRun();
            run.setText(ownText);
            setRunStyle(run, element);
        }


        //如果有子节点标签则继续读取
        if (null != element.children() && element.children().size() > 0) {
            for (Element el : element.children()) {
                readPTag(cell, xwpfParagraph, el);
            }
        }
    }

    /**
     * 设置Run字体样式
     *
     * @param run
     * @param element
     */
    private void setRunStyle(XWPFRun run, Element element) {
        Attributes attributes = element.attributes();
        if (null != attributes && attributes.size() > 0) {
            readAttributes(run, attributes);
        }
        readTagStyle(run, element);

    }

    /**
     * 设置行高
     *
     * @param row
     * @param height
     */
    private void setRowHeight(XWPFTableRow row, Integer height) {
        //设置网格属性
        CTTrPr trPr = row.getCtRow().addNewTrPr();
        trPr.addNewGridAfter();
        trPr.addNewGridBefore();
        CTHeight h = trPr.addNewTrHeight();
        h.setVal(BigInteger.valueOf(height));

    }


    /**
     * 设置单个表格宽高
     *
     * @param cell
     * @param width
     */
    private void setCellWith(XWPFTableCell cell, Integer width) {
        //设置列宽
        CTTcPr cPr1 = cell.getCTTc().addNewTcPr();
        CTTblWidth w = cPr1.addNewTcW();
        w.setW(BigInteger.valueOf(width));
        w.setType(STTblWidth.DXA);
    }

    /**
     * 批量设置表格宽高
     *
     * @param table
     */
    private void setCellWith(XWPFTable table) {
        for (XWPFTableRow row : table.getRows()) {
            row.setHeight(2300);
            for (XWPFTableCell cell : row.getTableCells()) {
                //设置列宽
                CTTcPr cPr1 = cell.getCTTc().addNewTcPr();
                CTTblWidth w = cPr1.addNewTcW();
                w.setW(BigInteger.valueOf(7000));
                w.setType(STTblWidth.DXA);
            }
        }
    }

    /**
     * 读取标签样式并设置run样式
     *
     * @param run
     * @param element
     */
    private void readTagStyle(XWPFRun run, Element element) {

        switch (element.tagName()) {
            case "i":
                run.setItalic(true);
                //System.out.println("获取到字体[倾斜]");
                break;
            case "strong":
                run.setBold(true);
                //System.out.println("获取到字体[加粗]");
                break;
            case "u":
                run.setUnderline(UnderlinePatterns.SINGLE);
                //System.out.println("获取到字体[下划线]");
                break;
            case "s":
                run.setStrikeThrough(true);
                //System.out.println("获取到字体[删除线]");
                break;

            default:

        }

    }

    /**
     * 读取标签属性
     *
     * @param run
     * @param attributes
     */
    private void readAttributes(XWPFRun run, Attributes attributes) {
        List<String> attrValues = new ArrayList<>();
        for (Iterator<Attribute> it = attributes.iterator(); it.hasNext(); ) {
            Attribute attribute = it.next();
            attrValues.add(attribute.getValue());
        }

        Map<String, String> attrs = new HashMap<>();
        for (String val : attrValues) {
            String[] attr = val.split(";");
            if (null != attr && attr.length > 0) {
                for (String val2 : attr) {
                    String[] kvs = val2.split(":");
                    if (null != kvs && kvs.length > 1) {
                        attrs.put(kvs[0].trim(), kvs[1].replace(" ", ""));
                    }
                }
            }
        }
        runSetStyle(attrs, run);

    }

    /**
     * 设置run空间 匹配到的字体样式
     *
     * @param attrs
     * @param run
     */
    private void runSetStyle(Map<String, String> attrs, XWPFRun run) {
        for (String key : attrs.keySet()) {
            switch (key) {
                case "font-family":
                    String family = attrs.get(key);
                    String[] f = family.split(",");
                    if (!StringUtils.isEmpty(f[0])) {
                        run.setFontFamily(f[0]);
                    }
                    //System.out.println("匹配到字体类型：" + attrs.get(key));
                    break;
                case "font-size":
                    String size = attrs.get(key).replace("px", "");
                    Integer fontSize = Integer.parseInt(size);
                    if (fontSize > 0) {
                        run.setFontSize(fontSize);
                    }
                    //System.out.println("匹配到字体大小：" + attrs.get(key));
                    break;
                case "text-align":
                    //System.out.println("匹配到字体浮动：" + attrs.get(key));
                    break;
                case "color":
                    String colorRGB = attrs.get(key);
                    //没有#号说明不是RGB颜色格式
                    if (colorRGB.contains("#")) {
                        java.awt.Color color = java.awt.Color.decode(colorRGB);
                        if (null != color) {
                            run.setColor(attrs.get(key).replace("#", ""));
                        }
                    }
                    //System.out.println("匹配到字体颜色：" + attrs.get(key));
                    break;
                default:
            }
        }
    }

    /**
     * 合并单元格后的区域坐标和样式
     */
    private class CrossRangeCellMeta {

        private int firstRowIndex;//开始行
        private int firstColIndex;//开始列
        private int rowSpan;// 跨越行数
        private int colSpan;// 跨越列数


        public CrossRangeCellMeta(int firstRowIndex, int firstColIndex, int rowSpan, int colSpan) {
            super();
            this.firstRowIndex = firstRowIndex;
            this.firstColIndex = firstColIndex;
            this.rowSpan = rowSpan;
            this.colSpan = colSpan;
        }

        /**
         * 开始行
         *
         * @return
         */
        int getFirstRow() {
            return firstRowIndex;
        }

        /**
         * 结束行
         *
         * @return
         */
        int getLastRow() {
            return firstRowIndex + rowSpan - 1;
        }

        /**
         * 开始列
         *
         * @return
         */
        int getFirstCol() {
            return firstColIndex;
        }

        /**
         * 结束列
         *
         * @return
         */
        int getLastCol() {
            return firstColIndex + colSpan - 1;
        }

        @Override
        public String toString() {
            return "CrossRangeCellMeta{" +
                    "开始行：=" + firstRowIndex +
                    ",开始列：=" + firstColIndex +
                    ", 结束行：=" + rowSpan +
                    ", 结束列：=" + colSpan +
                    '}';
        }
    }

}